﻿using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace CloudDetector.Utils
{
    /// <summary>
    /// Műholdképek sávok szerinti pixel mátrixainak tároló osztálya
    /// </summary>
    public class ImageLayers
    {
        #region Fields

        //a tasseled cap-ben a calc fv-ben az indok a public-ra
        internal byte[, ,] Layers;              //1.dim layerek, 2-3.dim a kép adot sávja
        private int width;
        private int height;
        private int LayerCount;

        #endregion

        #region Properties

        /// <summary>
        ///  A tárolt műholdkép szélessége.
        /// </summary>
        public int Width
        {
            get
            {
                return width;
            }
        }

        /// <summary>
        /// A tárolt műholdkép magassága.
        /// </summary>
        public int Height
        {
            get
            {
                return height;
            }
        }

        /// <summary>
        /// A tárolt műholkép sávjainak száma.
        /// </summary>
        public int Layercount
        {
            get
            {
                return LayerCount;
            }
        }

        /// <summary>
        /// A tárolt műholdkép sávjainak multidimenziós byte mátrixa.
        /// </summary>
        public byte[, ,] layers
        {
            get
            {
                return Layers;
            }
        }

        #endregion

        #region Contructors

        /// <summary>
        /// Constructor a Tasseled cap eredményét tartalmazó ImageLayers létrehozásához
        /// </summary>
        /// <param name="L">Layer szám</param>
        /// <param name="W">Szélesség</param>
        /// <param name="H">Magasság</param>
        public ImageLayers(int L, int W, int H)
        {
            width = W;
            height = H;
            LayerCount = L;
            Layers = new byte[LayerCount, W, H];

        }

        /// <summary>
        /// Constructor a Tasseled cap eredményét tartalmazó ImageLayers létrehozásához
        /// </summary>
        /// <param name="Source">Forrás tömb</param>
        public ImageLayers(float[, ,] Source)
        {
            LayerCount = Source.GetLength(0);
            width = Source.GetLength(1);
            height = Source.GetLength(2);
            Layers = new byte[LayerCount, width, height];


            float[] Min = new float[LayerCount];
            float[] Max = new float[LayerCount];

            //min&max ker
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                    for (int k = 0; k < LayerCount; ++k)
                    {
                        if (Min[k] > Source[k, i, j])
                            Min[k] = Source[k, i, j];
                        if (Max[k] < Source[k, i, j])
                            Max[k] = Source[k, i, j];
                    }

            float[] Rate = new float[LayerCount];

            for (int k = 0; k < LayerCount; ++k)
            {

                Rate[k] = 255 / (Max[k] - Min[k]);
            }

            //normalizálás [0, 255] közé
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                    for (int k = 0; k < LayerCount; ++k)
                    {
                        Layers[k, i, j] = Convert.ToByte(Rate[k] * (Source[k, i, j] - Min[k]));
                    }


        }

        /// <summary>
        /// Constructor a 2 input képes ImageLayers létrehozásához
        /// </summary>
        /// <param name="Picture1">Az első 3 sávos kép</param>
        /// <param name="Picture2">A második 3 sávos kép</param>
        public ImageLayers(Bitmap Picture1, Bitmap Picture2)
        {
            width = Picture1.Width;
            height = Picture1.Height;
            LayerCount = 6;

            Layers = new byte[LayerCount, width, height];

            BitmapData Picture1Data = Picture1.LockBits(new System.Drawing.Rectangle(0, 0, Picture1.Width, Picture1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            BitmapData Picture2Data = Picture2.LockBits(new System.Drawing.Rectangle(0, 0, Picture2.Width, Picture2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            unsafe
            {
                int Remain1 = Picture1Data.Stride - Picture1Data.Width * 3;
                int Remain2 = Picture2Data.Stride - Picture2Data.Width * 3;
                byte* PtrP1 = (byte*)Picture1Data.Scan0;
                byte* PtrP2 = (byte*)Picture2Data.Scan0;
                for (int j = 0; j < height; ++j)
                {
                    for (int i = 0; i < width; ++i)
                    {
                        Layers[0, i, j] = PtrP1[2];
                        Layers[1, i, j] = PtrP1[1];
                        Layers[2, i, j] = PtrP1[0];
                        Layers[3, i, j] = PtrP2[2];
                        Layers[4, i, j] = PtrP2[1];
                        Layers[5, i, j] = PtrP2[0];

                        PtrP1 += 3;
                        PtrP2 += 3;
                    }
                    PtrP1 += Remain1;
                    PtrP2 += Remain2;
                }
            }
            Picture1.UnlockBits(Picture1Data);
            Picture2.UnlockBits(Picture2Data);

        }

        /// <summary>
        /// Másoló konstruktor. A paraméterként kapott ImageLayers alapján létrehoz egy újat.
        /// </summary>
        /// <param name="sourceLayer">Forrás layers</param>
        public ImageLayers(ImageLayers sourceLayer)
        {
            width = sourceLayer.Width;
            height = sourceLayer.Height;
            LayerCount = sourceLayer.Layercount;
            Layers = new byte[LayerCount, width, height];

            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    for (int k = 0; k < LayerCount; k++)
                    {
                        Layers[k, i, j] = (sourceLayer.Layers)[k, i, j];
                    }
                }
            }
        }

        #endregion

        #region Member Methods

        /// <summary>
        /// ImageLayersből bitmap képbe átalakító.
        /// </summary>
        /// <param name="Picture1">Az első három sávot tartalmazó kép</param>
        /// <param name="Picture2">A második három sávot tartalmazó kép</param>
        public void CreateImage(out Bitmap Picture1, out Bitmap Picture2)
        {
            Picture1 = new Bitmap(width, height);
            Picture2 = new Bitmap(width, height);

            BitmapData Picture1Data = Picture1.LockBits(new System.Drawing.Rectangle(0, 0, Picture1.Width, Picture1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            BitmapData Picture2Data = Picture2.LockBits(new System.Drawing.Rectangle(0, 0, Picture2.Width, Picture2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            unsafe
            {
                int Remain1 = Picture1Data.Stride - Picture1Data.Width * 3;
                int Remain2 = Picture2Data.Stride - Picture2Data.Width * 3;
                byte* PtrP1 = (byte*)Picture1Data.Scan0;
                byte* PtrP2 = (byte*)Picture2Data.Scan0;
                for (int j = 0; j < height; ++j)
                {
                    for (int i = 0; i < width; ++i)
                    {
                        PtrP1[0] = Layers[2, i, j];
                        PtrP1[1] = Layers[1, i, j];
                        PtrP1[2] = Layers[0, i, j];
                        PtrP2[0] = Layers[5, i, j];
                        PtrP2[1] = Layers[4, i, j];
                        PtrP2[2] = Layers[3, i, j];

                        PtrP1 += 3;
                        PtrP2 += 3;
                    }
                    PtrP1 += Remain1;
                    PtrP2 += Remain2;
                }
            }
            Picture1.UnlockBits(Picture1Data);
            Picture2.UnlockBits(Picture2Data);
        }

        /// <summary>
        /// Adott sáv szürkeárnyalatos képpé alakítása.
        /// </summary>
        /// <param name="Layer">Az alakítandó sáv száma.</param>
        /// <returns></returns>
        public Bitmap CreateGrayScale(int Layer)
        {
            if (Layer + 1 > LayerCount)                 //mivel 0-tól indexelünk
                throw new Exception("Nincs ennyi layer!");

            Bitmap Picture1 = new Bitmap(width, height);
            BitmapData Picture1Data = Picture1.LockBits(new System.Drawing.Rectangle(0, 0, Picture1.Width, Picture1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            unsafe
            {
                int Remain1 = Picture1Data.Stride - Picture1Data.Width * 3;
                byte* PtrP1 = (byte*)Picture1Data.Scan0;
                for (int j = 0; j < height; ++j)
                {
                    for (int i = 0; i < width; ++i)
                    {

                        PtrP1[0] = PtrP1[1] = PtrP1[2] = Layers[Layer, i, j];

                        PtrP1 += 3;
                    }
                    PtrP1 += Remain1;
                }
            }
            Picture1.UnlockBits(Picture1Data);
            return Picture1;
        }

        /// <summary>
        ///  a 6 rétegű kép paraméterben kapott rétegeiből készít egy rgb képet
        ///  CSAK a forrás és az eredmény képekre!!!!
        /// </summary>
        /// <param name="R">a piros réteg sorszáma</param>
        /// <param name="G">a zöld réteg sorszáma</param>
        /// <param name="B">a kék réteg sorszáma</param>
        /// <returns>a 3 megadott rétegből készített Bitmap</returns>
        public Bitmap CreateRGB(int R, int G, int B)
        {
            if (R + 1 > LayerCount || G + 1 > LayerCount || B > LayerCount)                 //mivel 0-tól indexelünk
                throw new Exception("Nincs ennyi layer!");

            Bitmap Picture1 = new Bitmap(width, height);
            BitmapData Picture1Data = Picture1.LockBits(new System.Drawing.Rectangle(0, 0, Picture1.Width, Picture1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            unsafe
            {
                int Remain1 = Picture1Data.Stride - Picture1Data.Width * 3;
                byte* PtrP1 = (byte*)Picture1Data.Scan0;
                for (int j = 0; j < height; ++j)
                {
                    for (int i = 0; i < width; ++i)
                    {

                        PtrP1[2] = Layers[R, i, j];
                        PtrP1[1] = Layers[G, i, j];
                        PtrP1[0] = Layers[B, i, j];

                        PtrP1 += 3;
                    }
                    PtrP1 += Remain1;
                }
            }
            Picture1.UnlockBits(Picture1Data);
            return Picture1;
        }

        /// <summary>
        ///  a kézzel beállított küszöb értékekhez generál felhőt
        /// </summary>
        /// <param name="Thresholds">a 3 réteg küszöb értéke</param>
        /// <returns>felhőt tartalmazó ImageLayers</returns>
        public ImageLayers CreateCloud(int[] Thresholds)
        {
            ImageLayers Cloud = new ImageLayers(1, width, height);

            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                {
                    //a képpont egy felhőhöz tartozik
                    if (Layers[0, i, j] > Thresholds[0] &&     //br
                        Layers[1, i, j] < Thresholds[1] &&     //wn
                        Layers[2, i, j] > Thresholds[2])       //hz
                    {
                        (Cloud.layers)[0, i, j] = 255;
                    }
                    else
                    {
                        (Cloud.layers)[0, i, j] = 0;
                    }
                }

            return Cloud;
        }

        /// <summary>
        ///  módosított küszöb esetén újraszámolja az összes layert
        /// </summary>
        /// <param name="Thresholds">a 3 réteg küszöb értéke</param>
        /// <param name="Index">a 3 réteg küszöb értéke</param>
        /// <param name="Result">a kimeneti módosított kép</param>
        public void RecalcThresholdedLayer(int Threshold, int Index, ref ImageLayers Result)
        {
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                {
                    //a képpont egy felhőhöz tartozik
                    if (Layers[Index, i, j] > Threshold)
                        (Result.layers)[Index, i, j] = 255;
                    else
                        (Result.layers)[Index, i, j] = 0;
                }
        }

        /// <summary>
        /// Layer maszkolás. Működés elve: amelyik pixelen a maszk értéke 0 (azaz nincs felhő vagy árnyék), ott megszámolja
        /// a pixel intenzitást, ahol viszont a maszk értéke 255 (ott van felhő vagy árnyék) ott figyelmen kívülhagyja a 
        /// pixelintenzitást. Ezekből az intenzitásértékekből épít hisztogramot.
        /// </summary>
        /// <param name="Mask">A felhők és árnyékok maszkoló layer</param>
        /// <returns>Hisztogram mátrix. Sorai a kép sávjai, oszlopai a sávok hisztogramjai.</returns>
        public int[,] Masking(ImageLayers Mask)
        {
            int[,] Histogram = new int[LayerCount, 256];
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                    for (int k = 0; k < LayerCount; ++k)
                    {
                        //nem felhő, ha felhő nem kell vele foglalkozni
                        if ((Mask.layers)[0, i, j] == 0)
                            ++Histogram[k, Layers[k, i, j]];

                    }
            return Histogram;
        }

        /// <summary>
        /// Eredetik kép maszkolás. Működés elve: amelyik pixelen a maszk értéke 0 (azaz nincs felhő vagy árnyék), ott meghagyja az eredeti
        /// pixel intenzitást, ahol viszont a maszk értéke 255 (ott van felhő vagy árnyék) ott 0-t helyetteseít be
        /// </summary>
        /// <param name="Mask">a maszkoláshoz használt ImageLayers</param>
        /// <param name="R">a piros layer sorszáma</param>
        /// <param name="G">a zöld layer sorszáma</param>
        /// <param name="B">a kék layer sorszáma</param>
        /// <returns>maszkolt ImageLayers</returns>
        public Bitmap MaskedOriginals(ImageLayers Mask, int R, int G, int B)
        {

            if (R + 1 > LayerCount || G + 1 > LayerCount || B > LayerCount)                 //mivel 0-tól indexelünk
                throw new Exception("Nincs ennyi layer!");

            Bitmap Picture1 = new Bitmap(width, height);
            BitmapData Picture1Data = Picture1.LockBits(new System.Drawing.Rectangle(0, 0, Picture1.Width, Picture1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            unsafe
            {
                int Remain1 = Picture1Data.Stride - Picture1Data.Width * 3;
                byte* PtrP1 = (byte*)Picture1Data.Scan0;
                for (int j = 0; j < height; ++j)
                {
                    for (int i = 0; i < width; ++i)
                    {
                        //a képpont egy felhőhöz tartozik
                        if ((Mask.layers)[0, i, j] == 255)        //felhő
                        {
                            PtrP1[0] = Layers[B, i, j];
                            PtrP1[1] = Layers[G, i, j];
                            PtrP1[2] = Layers[R, i, j];
                        }
                        else                                    //nem felhő
                        {
                            PtrP1[0] = PtrP1[1] = PtrP1[2] = 0;
                        }
                        PtrP1 += 3;
                    }
                    PtrP1 += Remain1;
                }
            }
            Picture1.UnlockBits(Picture1Data);
            return Picture1;
        }

        /// <summary>
        /// A mask rétegek alapján készít egy értékes adatokat tartalmazó imagelayert.
        /// </summary>
        /// <param name="Mask">Felhők és árnyékok maszk</param>
        /// <returns>Értékes adatok layerei.</returns>
        public ImageLayers GetValueData(ImageLayers Mask)
        {
            ImageLayers Result = new ImageLayers(Layercount, Width, Height);
            for (int j = 0; j < height; ++j)
            {
                for (int i = 0; i < width; ++i)
                {
                    for (int k = 0; k < Layercount; k++)
                    {
                        if ((Mask.layers)[0, i, j] == 255)
                            (Result.layers)[k, i, j] = 0;
                        else
                            (Result.layers)[k, i, j] = layers[k, i, j];
                    }
                }
            }
            return Result;
        }

        /// <summary>
        /// Visszaadja a tárolt kép rétegenkénti hisztogramjait.
        /// </summary>
        /// <returns>Rétegenkénti hisztogram mátrix.</returns>
        public int[,] Histograms()
        {
            int[,] Histogram = new int[LayerCount, 256];
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i)
                    for (int k = 0; k < LayerCount; ++k)
                    {
                        ++Histogram[k, Layers[k, i, j]];
                    }
            return Histogram;
        }

        /// <summary>
        /// A felhőn belüli hibás nem_felhő pixelek kiszűrése és felhővé "alakítása"
        /// a felhővel való dolgozás elött hívandó meg!!
        /// </summary>
        public void CleanCloud()
        {
            //nem felhő
            if (LayerCount > 1)
                throw new Exception("Nem felhőre lett meghívva a fügvény!");

            bool Felho = false;
            for (int j = 0; j < height; ++j)
            {
                Felho = (layers[0, 0, j] == 255);
                for (int i = 1; i < width - 1; ++i)
                {
                    if (Felho && layers[0, i, j] == 0)
                    {
                        if (CountNCPixels(i, j) <= 20)
                            layers[0, i, j] = 255;
                        else
                            Felho = false;
                    }
                    //ha megtaláltuk egy felhő szélét
                    else if (!Felho && layers[0, i - 1, j] != 0)
                    {
                        Felho = true;
                    }
                }
            }
        }

        private int CountNCPixels(int X, int Y)
        {
            //ne lépjen le a képről
            if (X >= width || Y >= height || X < 0 || Y < 0)
                return 0;
            if (X == 62 && Y == 391)
            { }
            //ha felhőt talált
            if (Layers[0, X, Y] != 0)
                return 0;
            int Tmp = 1;
         //   Layers[0, X, Y]++;                  //itt már jártam ne számolja meg megint
            Tmp += CountNCPixels(X + 1, Y);  //mellette lévő pixel
            //20dbnál több pixel már valószínű nem hiba, hanem tényeleg nincs ott felhő
            if (Tmp > 20)
                return Tmp;
            for (int i = -1; i < 2; ++i)        //az alatta levőkhöz: elötte, közbetlenül alatta, utána
            {
                Tmp += CountNCPixels(X + i, Y + 1);
                if (Tmp > 20)
                    return Tmp;
            }
            return Tmp;
        }

        #endregion
    }
}
